﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public class Constructor : ICodeContainer
	{
		public bool IsStatic { get; private set; }
		public MethodParam[] Params { get; private set; }
		public Executable[] RestOfCode { get; private set; }
		public int ID { get; set; }
		public Expression[] BaseClassConstructorParams { get; private set; }
		public Executable[] ParamSettingCode { get; private set; }
		public Executable[] Code { get; private set; }
		public string Filename { get; private set; }

		private Class owningClass;

		public Constructor(bool isStatic, MethodParam[] paramList, IList<Executable> code, IList<Expression> baseClassConstructorParams, string filename)
		{
			this.Filename = filename;

			this.IsStatic = isStatic;
			this.Params = paramList;

			List<Executable> allCode = new List<Executable>();
			// TODO: prepend all default value fields to the beginning before default parameters. 

			List<Executable> paramHeader = Method.GetParamSettingForMethodBodyHead(paramList);
			this.ParamSettingCode = paramHeader.ToArray();

			this.RestOfCode = Executable.AppendReturnIfNecessary(code);
			this.BaseClassConstructorParams = baseClassConstructorParams == null
				? Expression.EMPTY_LIST
				: baseClassConstructorParams.ToArray();

			this.Code = this.RestOfCode.ToArray();
		}

		public string FullSignature
		{
			get
			{
				string signature = this.owningClass.FullName + "[" + (this.IsStatic ? "static " : "") + "constructor]" + "(";
				for (int i = 0; i < this.Params.Length; ++i)
				{
					if (i > 0)
					{
						signature += ", ";
					}

					signature += this.Params[i].Name;
				}
				signature += ")";
				return signature;
			}
		}

		// Adds the base class call to the top of the constructor code
		// followed by field initialization for just the fields local to this class.
		// Field name collision in subclasses is forbidden.
		// Calls to the base class constructor are required or implicitly added.
		// This must be called after metadata has been resolved.
		public void AddHeaderToConstructor(Class parent, Compiler c)
		{
			List<Executable> code = new List<Executable>();

			code.AddRange(this.ParamSettingCode);
			
			Class baseClass = parent.BaseClassInstance;
			if (baseClass != null)
			{
				// There is a base class. Call the base class constructor.
				Executable firstLine = new ExpressionAsExecutable(new BaseClassConstructorCall(this.BaseClassConstructorParams, baseClass));
				//throw new NotImplementedException("Need a new op code to call constructor with an existing context");
				code.Add(firstLine);
			}

			// defaulting to true for 3rd param because that was the old behavior TODO: reanalyze whether it's needed here.
			foreach (object maybeField in parent.GetMembers(true, false, true))
			{
				Field field = maybeField as Field;
				if (field != null && !field.IsStatic)
				{
					// TODO: verifiy that the default value does not include "this"
					code.Add(new Assignment(new DotDereference(new ThisValue(null), field.Name, null), field.DefaultValue, '=', null));
				}
			}

			code.AddRange(this.RestOfCode);

			this.Code = code.ToArray();
		}

		public static Constructor Parse(Tokens tokens)
		{
			bool isStatic = tokens.PopIfPresent("static");
			Token token = tokens.PopExpected("constructor");
			tokens.PopExpected("(");
			MethodParam[] paramList = MethodParam.ParseList(tokens);
			tokens.PopExpected(")");
			Expression[] baseClassConstructorParams = null;

			if (tokens.PopIfPresent(":"))
			{
				tokens.PopExpected("base");
				tokens.PopExpected("(");

				baseClassConstructorParams = Expression.ParseCommaList(tokens);

				tokens.PopExpected(")");
			}

			List<Executable> code = new List<Executable>(CodeBlock.Parse(tokens, true));
			return new Constructor(isStatic, paramList, code, baseClassConstructorParams, token.RelativeFile);
		}

		public void ResolveCode(Class c, Dictionary<object, object> parents)
		{
			this.owningClass = c;

			foreach (MethodParam p in this.Params)
			{
				if (p.Value != null)
				{
					p.Value.ResolveCode(c, parents);
				}
			}

			foreach (Executable ex in this.ParamSettingCode.Concat<Executable>(this.RestOfCode))
			{
				ex.ResolveCode(c, parents);
			}

			if (this.IsStatic)
			{
				// TODO: recurse and find any instances of "this". Throw a compiler error if there are.

				// TODO: extract all code references to this class and replace them with "this"
				// This prevents the static initializer from infinitely recursing.
			}
		}

		public void ResolveClassToThis(Class c)
		{
			if (this.IsStatic) // there is no reason this should run for non static constructors
			{
				foreach (Executable ex in this.Code)
				{
					ex.ResolveClassToThis(c);
				}

				foreach (MethodParam p in this.Params)
				{
					p.ResolveClassToThis(c);
				}
			}
		}
	}
}
